package com.xneure.database.extend;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.binding.MapperProxy;
import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import com.xneure.utils.ObjectUtil;

public class MapperParamProxy extends MapperProxy<T>{
	private static final long serialVersionUID = -7410491243254104778L;
	private final SqlSession sqlSession;
	private final Class<T> mapperInterface;
	private LocalVariableTableParameterNameDiscoverer lvtpn = new LocalVariableTableParameterNameDiscoverer();
	private final Map<Method,MapperMethod> methodCache;

	public MapperParamProxy(MapperProxy<T> mp){
		this(ObjectUtil.getFieldValue(mp,"sqlSession"),ObjectUtil.getFieldValue(mp,"mapperInterface"),ObjectUtil.getFieldValue(mp,"methodCache"));
	}

	public MapperParamProxy(SqlSession sqlSession,Class<T> mapperInterface,Map<Method,MapperMethod> methodCache){
		super(null,null,null);
		this.sqlSession = sqlSession;
		this.mapperInterface = mapperInterface;
		this.methodCache = methodCache;
	}

	@Override
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		try{
			if(Object.class.equals(method.getDeclaringClass())){
				return method.invoke(this,args);
			}else if(isDefaultMethod(method)){
				return invokeDefaultMethod(proxy,method,args);
			}
		}catch(Throwable t){
			throw ExceptionUtil.unwrapThrowable(t);
		}
		final MapperMethod mapperMethod = cachedMapperMethod(method);
		return mapperMethod.execute(sqlSession,args);
	}

	private MapperMethod cachedMapperMethod(Method method){
		MapperMethod mapperMethod = methodCache.get(method);
		if(mapperMethod == null){
			mapperMethod = new MapperMethod(mapperInterface,method,sqlSession.getConfiguration());
			Map<Integer,String> paramMap = ObjectUtil.getFieldValue(mapperMethod,"method","paramNameResolver","names");
			String[] param = lvtpn.getParameterNames(method);
			if(null != param){
				for(int i = 0;i < param.length;i++){
					if(paramMap.get(i).equals(param[i]) == false){
						paramMap.put(i,param[i]);
					}
				}
			}
			methodCache.put(method,mapperMethod);
		}
		return mapperMethod;
	}

	@UsesJava7
	private Object invokeDefaultMethod(Object proxy,Method method,Object[] args) throws Throwable{
		final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class,int.class);
		if(!constructor.isAccessible()){
			constructor.setAccessible(true);
		}
		final Class<?> declaringClass = method.getDeclaringClass();
		return constructor
				.newInstance(declaringClass,
						MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
				.unreflectSpecial(method,declaringClass).bindTo(proxy).invokeWithArguments(args);
	}

	private boolean isDefaultMethod(Method method){
		return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC && method.getDeclaringClass().isInterface();
	}
}
